import { defineComponent, ref, computed, watch, watchEffect, onMounted, onBeforeUnmount, provide, createVNode, Fragment, Teleport, mergeProps, isVNode } from 'vue';
import usePortal from '../use/usePortal.js';
import useAlignPostion from '../use/useAlignPostion.js';
import useClickOutside from '../use/useClickOutside.js';
import usezIndex from '../use/usezIndex.js';
import useTransition from '../use/useTransition.js';
import DropdownItem from './DropdownItem.js';
import { useDebounce } from '../use/useDebounce.js';
import { isColor, uuidv4 } from '../utils/assist.js';
import { useMoveObserver } from '../use/useMoveObserver.js';
import DropdownMenu from './DropdownMenu.js';

function _isSlot(s) {
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s);
}
const Dropdown = /* @__PURE__ */ defineComponent({
  name: 'Dropdown',
  props: {
    trigger: {
      type: String,
      default: 'hover'
    },
    align: {
      type: String,
      default: 'bottom'
    },
    theme: {
      type: String
    },
    menu: {
      type: Object
    },
    modelValue: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    },
    revers: {
      type: Boolean,
      default: true
    },
    handler: {
      type: String,
      default: undefined
    },
    fixWidth: {
      type: Boolean,
      default: false
    },
    gradient: {
      type: Array,
      default: () => []
    },
    color: {
      type: String,
      default: undefined
    },
    arrow: {
      type: Boolean,
      default: false
    },
    offset: {
      type: Number,
      default: 0
    },
    position: {
      type: Object,
      default: undefined
    },
    onBeforeDrop: {
      type: Function
    },
    data: {
      type: Array,
      default: undefined
    },
    transfer: {
      type: Boolean,
      default: true
    }
  },
  emits: ['select', 'update:modelValue', 'mouseClick', 'visibleChange'],
  setup(props, {
    attrs,
    emit,
    slots,
    expose
  }) {
    const visible = ref(props.modelValue);
    const opened = ref(visible);
    const placement = ref(props.align);
    const _ = ref("_");
    let nextElementSibling;
    let targetEle;
    const target = ref();
    let timer;
    const wrap = ref();
    const zindex = usezIndex();
    const revers = props.revers ?? true;
    const classList = computed(() => ({
      'cm-dropdown': true,
      [attrs.class]: true,
      [`cm-dropdown-${props.theme}`]: props.theme,
      'cm-dropdown-with-arrow': props.arrow
    }));
    let lastEventTriggerTarget;
    let cleanup = null;
    const transition = useTransition({
      el: () => wrap.value,
      startClass: 'cm-dropdown-visible',
      activeClass: 'cm-dropdown-open',
      onLeave: () => {
        opened.value = false;
        if (cleanup) {
          cleanup();
        }
        emit('visibleChange', false);
      },
      onEnter: () => {
        opened.value = true;
        // 监控元素移动
        cleanup = useMoveObserver(wrap.value, () => {
          _.value = uuidv4();
        });
        emit('visibleChange', true);
      }
    });
    watch(() => props.modelValue, val => {
      visible.value = val;
    });
    watch(visible, v => {
      if (v) {
        transition.enter();
      } else {
        transition.leave();
      }
    });

    // 防抖
    const clearDelayTimer = () => {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
    };

    // 点击显示
    const onMouseClick = e => {
      if (!nextElementSibling.contains(e.target)) {
        return false;
      }
      if (props.handler) {
        const te = document.querySelector(props.handler);
        if (!te) {
          return;
        }
        if (!e.target.closest(props.handler) && !te.contains(e.target)) {
          return;
        }
      }
      if (props.disabled) {
        return;
      }
      e.preventDefault && e.preventDefault();
      e.stopPropagation && e.stopPropagation();
      targetEle = e.target;
      emit('mouseClick', e);
      const ret = props.onBeforeDrop && props.onBeforeDrop(visible.value);
      if (ret === undefined || ret) {
        // 触发的对象不一致，则重新定位，可能是使用handle进行触发,有多个触发元素的情况
        if (props.handler && lastEventTriggerTarget !== e.target.closest(props.handler)) {
          _.value = uuidv4();
        }
        visible.value = true;
        emit('update:modelValue', visible.value);
        if (props.handler) {
          lastEventTriggerTarget = e.target.closest(props.handler);
        }
      }
    };
    const onMouseEnter = () => {
      if (props.disabled) {
        return;
      }
      if (props.trigger === 'hover') {
        clearDelayTimer();
        visible.value = true;
        emit('update:modelValue', true);
      }
    };
    const onMouseLeave = () => {
      if (props.disabled) {
        return;
      }
      if (props.trigger === 'hover') {
        timer = setTimeout(() => {
          visible.value = false;
          emit('update:modelValue', false);
        }, 200);
      }
    };
    const getOffsetWidth = (align, rect) => {
      if (align === 'bottomRight' || align === 'topRight') {
        return 0;
      }
      if (align === 'top' || align === 'bottom') {
        return rect.width / 2;
      }
      if (align === 'topLeft' || align === 'bottomLeft') {
        return rect.width;
      }
      if (align === 'left' || align === 'leftTop' || align === 'leftBottom') {
        return 0;
      }
      if (align === 'right' || align === 'rightTop' || align === 'rightBottom') {
        return rect.width;
      }
    };
    const getOffsetHeight = (align, rect) => {
      if (align === 'leftBottom' || align === 'rightBottom') {
        return 0;
      }
      if (align === 'top' || align === 'topLeft' || align === 'topRight') {
        return 0;
      }
      if (align === 'leftTop' || align === 'rightTop') {
        return rect.height;
      }
      if (align === 'left' || align === 'right') {
        return rect.height / 2;
      }
      if (align === 'bottom' || align === 'bottomLeft' || align === 'bottomRight') {
        return rect.height;
      }
    };
    const getPositionByPlacement = (align, te) => {
      const parent = te.offsetParent;
      if (!parent) {
        return;
      }
      const parentPos = parent.getBoundingClientRect();
      const pos = useAlignPostion(align, te);
      if (props.transfer) {
        const targetReact = te.getBoundingClientRect();
        pos.top = pos.top + document.documentElement.scrollTop;
        pos.left = pos.left + document.documentElement.scrollLeft;
        props.fixWidth ? pos['min-width'] = targetReact.width + 'px' : false;
      } else {
        pos.top = pos.top + parent.scrollTop - parentPos.top;
        pos.left = pos.left + parent.scrollLeft - parentPos.left;
      }
      return pos;
    };
    const posStyle = ref({});
    watchEffect(() => {
      _.value;
      if (opened.value && nextElementSibling) {
        let te = nextElementSibling;
        if (props.handler) {
          te = targetEle?.closest(props.handler);
        }
        if (!te) {
          return;
        }
        const parent = te.offsetParent;
        if (!parent) {
          return;
        }
        if (props.position) {
          const pos = {
            left: props.position.x + 'px',
            top: props.position.y + 'px'
          };
          Object.assign(pos, attrs.style || {}, {
            '--cui-dropdown-background-color': isColor(props.theme) ? props.theme : '',
            '--cui-dropdown-text-color': props.color,
            '--cui-dropdown-offset': `${props.offset}px`
          });
          posStyle.value = pos;
          return pos;
        }
        const parentPos = parent.getBoundingClientRect();
        let pos = useAlignPostion(props.align, te);
        const originTop = pos.top;
        const originLeft = pos.left;
        if (props.transfer) {
          const targetReact = te.getBoundingClientRect();
          pos.top = pos.top + document.documentElement.scrollTop;
          pos.left = pos.left + document.documentElement.scrollLeft;
          props.fixWidth ? pos['min-width'] = targetReact.width + 'px' : false;
        } else {
          pos.top = pos.top + parent.scrollTop - parentPos.top;
          pos.left = pos.left + parent.scrollLeft - parentPos.left;
        }
        const rect = wrap.value.getBoundingClientRect();
        const offsetWidth = getOffsetWidth(props.align, rect);
        const offsetHeight = getOffsetHeight(props.align, rect);
        const h = originTop + offsetHeight;
        const w = originLeft + offsetWidth;
        const containerHeight = window.innerHeight || document.documentElement.clientHeight;
        const containerWidth = window.innerWidth || document.documentElement.clientWidth;
        if (revers) {
          let newAlign = props.align;
          if (h > containerHeight) {
            if (props.align === 'bottom' || props.align === 'bottomLeft' || props.align === 'bottomRight') {
              newAlign = props.align.replace('bottom', 'top');
            } else if (props.align === 'leftTop' || props.align === 'left') {
              newAlign = 'leftBottom';
            } else if (props.align === 'right' || props.align === 'rightTop') {
              newAlign = 'rightBottom';
            }
          }
          // align 为 top bottom topLeft bottomLeft right rightTop rightBottom 存在该情况
          if (w > containerWidth - 5) {
            if (props.align === 'bottom' || props.align === 'bottomLeft') {
              // pos.left = pos.left - (rect.width - targetRect.width) / 2;
              newAlign = 'bottomRight';
            } else if (props.align === 'top' || props.align === 'topLeft') {
              // pos.left = pos.left - rect.width + targetRect.width;
              newAlign = 'topRight';
            } else if (props.align === 'right') {
              // pos.left = pos.left - rect.width - targetRect.width;
              newAlign = 'left';
            } else if (props.align === 'rightTop') {
              // pos.left = pos.left - rect.width - targetRect.width;
              newAlign = 'leftTop';
            }
          }
          if (newAlign !== props.align) {
            pos = getPositionByPlacement(newAlign, te);
            placement.value = newAlign;
          } else {
            pos = getPositionByPlacement(props.align, te);
            placement.value = props.align;
          }
        }
        pos.top = pos.top + 'px';
        pos.left = pos.left + 'px';
        pos['z-index'] = zindex;
        Object.assign(pos, attrs.style || {}, {
          '--cui-dropdown-background-color': isColor(props.theme) ? props.theme : '',
          '--cui-dropdown-text-color': props.color,
          '--cui-dropdown-offset': `${props.offset}px`
        });
        posStyle.value = pos;
        // return pos;
      }
    });
    expose({
      update: () => {
        _.value = uuidv4();
      }
    });

    // 响应尺寸变化更新位置
    const onWrapEntry = useDebounce(entry => {
      _.value = uuidv4();
    }, 100);
    let removeClickOutside;
    onMounted(() => {
      nextElementSibling = target.value.nextElementSibling;
      target.value.parentNode.removeChild(target.value);
      if (nextElementSibling) {
        if (props.trigger === 'hover') {
          nextElementSibling.addEventListener('mouseenter', onMouseEnter, false);
          nextElementSibling.addEventListener('mouseleave', onMouseLeave, false);
        }
        if (props.trigger === 'click' || props.trigger === 'custom') {
          document.addEventListener('click', onMouseClick);
          if (props.trigger === 'click') {
            const other = props.handler ? nextElementSibling.querySelectorAll(props.handler) : nextElementSibling;
            removeClickOutside = useClickOutside([wrap.value, other], () => {
              visible.value = false;
              emit('update:modelValue', false);
            });
          }
        }
        if (props.trigger === 'contextMenu') {
          document.addEventListener('contextmenu', onMouseClick);
          const other = props.handler ? nextElementSibling.querySelectorAll(props.handler) : nextElementSibling;
          removeClickOutside = useClickOutside([wrap.value, other], () => {
            visible.value = false;
            emit('update:modelValue', false);
          });
        }
      }
      const ro = new ResizeObserver(entries => {
        entries.forEach(entry => onWrapEntry(entry));
      });
      // 目标元素尺寸变化需要更新位置
      ro.observe(nextElementSibling);
      onBeforeUnmount(() => {
        ro.disconnect();
      });
    });
    onBeforeUnmount(() => {
      if (nextElementSibling) {
        if (props.trigger === 'hover') {
          nextElementSibling.removeEventListener('mouseenter', onMouseEnter);
          nextElementSibling.removeEventListener('mouseleave', onMouseLeave);
        }
        if (props.trigger === 'click' || props.trigger === 'custom') {
          document.removeEventListener('click', onMouseClick);
        }
        if (props.trigger === 'contextMenu') {
          document.removeEventListener('contextmenu', onMouseClick);
        }
      }
      removeClickOutside && removeClickOutside();
      if (cleanup) {
        cleanup();
      }
    });
    const onSelect = name => {
      emit('select', name);
      visible.value = false;
      emit('update:modelValue', false);
    };
    const renderMenu = arr => {
      if (arr) {
        let _slot;
        return createVNode(DropdownMenu, null, _isSlot(_slot = arr.map(item => {
          const {
            title,
            children,
            ...rest
          } = item;
          return createVNode(DropdownItem, mergeProps(rest, {
            "data": item,
            "arrow": !!children?.length
          }), {
            default: () => [title, children?.length ? renderMenu(children) : null]
          });
        })) ? _slot : {
          default: () => [_slot]
        });
      }
      return null;
    };
    provide('dropdownConext', {
      onSelect,
      gradient: props.gradient,
      color: props.color
    });
    const id = 'cm-dropdown-portal';
    return () => createVNode(Fragment, null, [createVNode("span", {
      "ref": target,
      "style": {
        display: 'none'
      }
    }, null), slots.default?.(), props.transfer ? createVNode(Teleport, {
      "to": usePortal(id, id)
    }, {
      default: () => [createVNode("div", {
        "style": posStyle.value,
        "class": classList.value,
        "x-placement": placement.value,
        "onMouseenter": onMouseEnter,
        "onMouseleave": onMouseLeave,
        "ref": wrap
      }, [props.menu || (slots.menu ? slots.menu() : null), renderMenu(props.data), props.arrow ? createVNode("svg", {
        "width": "24",
        "height": "8",
        "xmlns": "http://www.w3.org/2000/svg",
        "class": "cm-dropdown-arrow"
      }, [createVNode("path", {
        "d": "M0.5 0L1.5 0C1.5 4, 3 5.5, 5 7.5S8,10 8,12S7 14.5, 5 16.5S1.5,20 1.5,24L0.5 24L0.5 0z",
        "opacity": "1"
      }, null), createVNode("path", {
        "d": "M0 0L1 0C1 4, 2 5.5, 4 7.5S7,10 7,12S6 14.5, 4 16.5S1,20 1,24L0 24L0 0z"
      }, null)]) : null])]
    }) : createVNode("div", {
      "style": posStyle.value,
      "class": classList.value,
      "x-placement": placement.value,
      "onMouseenter": onMouseEnter,
      "onMouseleave": onMouseLeave,
      "ref": wrap
    }, [props.menu || (slots.menu ? slots.menu() : null), renderMenu(props.data), props.arrow ? createVNode("svg", {
      "width": "24",
      "height": "8",
      "xmlns": "http://www.w3.org/2000/svg",
      "class": "cm-dropdown-arrow"
    }, [createVNode("path", {
      "d": "M0.5 0L1.5 0C1.5 4, 3 5.5, 5 7.5S8,10 8,12S7 14.5, 5 16.5S1.5,20 1.5,24L0.5 24L0.5 0z",
      "opacity": "1"
    }, null), createVNode("path", {
      "d": "M0 0L1 0C1 4, 2 5.5, 4 7.5S7,10 7,12S6 14.5, 4 16.5S1,20 1,24L0 24L0 0z"
    }, null)]) : null])]);
  }
});

export { DropdownItem, DropdownMenu, Dropdown as default };
